#region Using References

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Windows.Input;
using Beaker.OpenCube.Nbt;
using Beaker.OpenCube.NbtEditor.Messages;
using GalaSoft.MvvmLight.Command;

#endregion

namespace Beaker.OpenCube.NbtEditor.ViewModels
{
	public sealed class MainViewModel
		: ViewModel
	{
		public MainViewModel()
		{
			Contract.Ensures( _selectPathCommand != null );
			_selectPathCommand = new RelayCommand( SelectPath );
		}

		[ContractInvariantMethod]
		private void ContractInvariant()
		{
			Contract.Invariant( _selectPathCommand != null );
			Contract.Invariant( _nodes != null );
		}

		#region Path

		private string _path;

		/// <summary>
		/// Gets or sets the path of the file to load.
		/// </summary>
		/// <value>
		/// The path of the file to load.
		/// </value>
		public string Path
		{
			get
			{
				Contract.Ensures( Contract.Result<string>() == _path );
				return _path;
			}
			set
			{
				Contract.Ensures( _path == value );
				SetAndRaiseIfChanged( ref _path, value, OnPathChanged );
			}
		}

		/// <summary>
		/// Gets a value indicating whether the path is invalid.
		/// </summary>
		/// <value>
		/// <c>true</c> if  the path is invalid; otherwise, <c>false</c>.
		/// </value>
		public bool IsPathInvalid
		{
			get { return !File.Exists( Path ); }
		}


		/// <summary>
		/// Called when the <see cref="Path"/> property has changed.
		/// </summary>
		private void OnPathChanged( string oldPath, string newPath )
		{
			// when path changes, IsPathInvalid should be rechecked, so raise change
			RaisePropertyChanged( () => IsPathInvalid );

			// if new path is not invalid, try to load the given file
			if( !IsPathInvalid )
				LoadFile( Path );
		}

		private readonly RelayCommand _selectPathCommand;

		/// <summary>
		/// Gets the SelectPath command.
		/// </summary>
		/// <value>
		/// The SelectPath command.
		/// </value>
		public ICommand SelectPathCommand
		{
			get
			{
				Contract.Ensures( Contract.Result<ICommand>() != null );
				Contract.Ensures( Contract.Result<ICommand>() == _selectPathCommand );
				return _selectPathCommand;
			}
		}

		/// <summary>
		/// Start selecting a path
		/// </summary>
		private void SelectPath()
		{
			// send file selector message, so a dialog to select a file is shown
			Send(
				new ShowViewMessage(
					new FileSelectorViewModel(),
					vm => OnPathSelected( (FileSelectorViewModel)vm )
				)
			);
		}
		/// <summary>
		/// Called when a path is selected via the dialog popup
		/// </summary>
		/// <param name="viewModel">The view model.</param>
		private void OnPathSelected( FileSelectorViewModel viewModel )
		{
			Contract.Requires<ArgumentNullException>( viewModel != null, "A viewmodel is required" );

			// when a result is returneD, set the path to that result
			if( viewModel.ResultPath != null )
				Path = viewModel.ResultPath;
		}

		#endregion
		#region Data

		/// <summary>
		/// Loads the file with the given path.
		/// </summary>
		/// <param name="path">The path of the file to load.</param>
		private void LoadFile( string path )
		{
			Contract.Requires<ArgumentNullException>( path != null, "A path is required" );

			if( File.Exists( path ) )
			{
				try
				{
					using( Stream stream = File.OpenRead( path ) )
					{
						SetContent( NbtReader.Deserialize( stream ) );
					}
				}
				catch( NbtFileFormatException )
				{
					Debugger.Break();
				}
			}
		}

		private readonly ObservableCollection<KeyValuePair<string, object>> _nodes =
			new ObservableCollection<KeyValuePair<string, object>>();

		/// <summary>
		/// Gets the collectcion of nodes to display.
		/// </summary>
		/// <value>
		/// The collection of nodes to display.
		/// </value>
		public ObservableCollection<KeyValuePair<string, object>> Nodes
		{
			get
			{
				Contract.Ensures( Contract.Result<ObservableCollection<KeyValuePair<string, object>>>() != null );
				return _nodes;
			}
		}

		private void SetContent( NbtDictionary dictionary )
		{
			Contract.Requires<ArgumentNullException>( dictionary != null, "A dictionary is required" );

			_nodes.Clear();

			foreach( KeyValuePair<string, object> pair in dictionary )
				_nodes.Add( pair );
		}

		#endregion
	}
}